#include "OSCthulhuServer.h"
#include "User.h"
#include "QWriteLocker"
#include "QReadLocker"
#include "SyncObject.h"
#include <QDebug>
#include "QVariant"

//ADD SAFE GUARDS TO ACCESSING THE ARGUMENT LISTS PAST THE NUMBER OF ARGUMENTS
OSCthulhuServer::OSCthulhuServer(int serverPort = 32242,QString serverPassword = "lagpass")
    : syncPacket(syncBuffer, SYNC_OUTPUT_BUFFER_SIZE)
{
    this->serverPort = serverPort;
    this->serverPassword = serverPassword;
    qDebug() <<"OSCthulhu Server started. ";
    qDebug()    <<"Server port = " << this->serverPort;
    oscThread = new ServerOSCThread(this);
    keepAlive = new KeepAlive(this);
    this->stateSync = new StateSynchronization(this);
}

void OSCthulhuServer::createUser(const char *serverPassword, const char *userName, const char *userPassword,QString address, float port, QString publicPort)
{
    QWriteLocker locker(&lock);
    if(QString(serverPassword).compare(this->serverPassword)==0) {
        qDebug() << "Server password correct, creating user " << userName;
        if(!users.contains(QString(userName))) {
            users.insert(
                QString(userName),
                new User(address,QString(userName),QString(userPassword),publicPort.toInt())
            );
        }
        char buffer[OUTPUT_BUFFER_SIZE];
        osc::OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE );
        p << osc::BeginMessage( "/notifyOfLogin" )
            << userName
            << osc::EndMessage;
        qDebug() << "Current Users(" << users.size() <<"): ";
        QHashIterator<QString, QPointer<User> > i(users);
        while (i.hasNext()) {
             i.next();
             qDebug() << qPrintable(i.key());
             if(i.value()) {
                //i.value()->sendUser(p);
                 this->oscThread->send(i.value().data()->getIp(),p);
             }
        }
        i.toFront();
        p.Clear();
        p << osc::BeginMessage( "/currentUsers" );
        while (i.hasNext()) {
             i.next();
             p << qPrintable(i.key());
        }
        p << osc::EndMessage;
        i.toFront();
        while (i.hasNext()) {
             i.next();
             if(i.value()) {
               //i.value()->sendUser(p);
                 this->oscThread->send(i.value().data()->getIp(),p);
             }
        }
    } else {
        qDebug() << "Incorrect server password, NOT creating user " << userName;
    }
}

void OSCthulhuServer::disconnectUser(const char *serverPassword, const char *userName, const char *userPassword, float port)
{
    QWriteLocker locker(&lock);
    if(QString(serverPassword).compare(this->serverPassword)==0) {
        if(users.contains(QString(userName))) {
            if(users.value(QString(userName))->getUserPassword().compare(QString(userPassword))==0) {
                if(users.value(QString(userName))) {
                    delete (users.value(QString(userName)));
                }
                users.remove(QString(userName));
                char buffer[OUTPUT_BUFFER_SIZE];
                osc::OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE );
                p << osc::BeginMessage( "/notifyOfLogout" )
                    << userName
                    << osc::EndMessage;
                qDebug() << "Current Users(" << users.size() <<"): ";
                QHashIterator<QString,QPointer<User> > i(users);
                while (i.hasNext()) {
                     i.next();
                     qDebug() << qPrintable(i.key());
                     if(i.value()) {
                        //i.value()->sendUser(p);
                        this->oscThread->send(i.value().data()->getIp(),p);
                     }
                }
                i.toFront();
                p.Clear();
                p << osc::BeginMessage( "/currentUsers" );
                while (i.hasNext()) {
                     i.next();
                     p << qPrintable(i.key());
                }
                p << osc::EndMessage;
                i.toFront();
                while (i.hasNext()) {
                     i.next();
                     if(i.value()) {
                        //i.value()->sendUser(p);
                         this->oscThread->send(i.value().data()->getIp(),p);
                     }
                }
            } else {
                qDebug() << "User Password for user: " << userName << "is incorrect, NOT disconnecting.";
            }
        } else {
            qDebug() << "User: " << userName << " does not exist, therefore cannot be disconnected.";
        }
    } else {
        qDebug() << "Incorrect server password, NOT disconnecting user " << userName;
    }
}
void OSCthulhuServer::sendAlive(int waitTime) {
    QReadLocker locker(&lock);
    QHashIterator<QString, QPointer<User> > i(users);
    char buffer[OUTPUT_BUFFER_SIZE];
    osc::OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE );
    p << osc::BeginMessage( "/areYouAlive?" )
        << osc::EndMessage;
    //cout << "Current Users(" << users.size() <<"): " << endl;
    while (i.hasNext()) {
         i.next();
         if(i.value()) {
            //i.value()->sendUser(p);
            this->oscThread->send(i.value().data()->getIp(),p);
         }
    }
}

void OSCthulhuServer::removeDead(int waitTime) {
    //QWriteLocker locker(&lock);
    QHashIterator<QString,QPointer<User> > i(users);
    while (i.hasNext()) {
        lock.lockForRead();
        i.next();
        if(i.value()) {
        i.value()->increaseAliveTimer(waitTime);
        if(i.value()->getAliveTimer() > 30) {
           lock.unlock();
           qDebug() << "User " << qPrintable(i.value()->getUserName()) << " not responding to Alive messages.";
           disconnectUser(
               qPrintable(serverPassword),
               qPrintable(i.value()->getUserName()),
               qPrintable(i.value()->getUserPassword()),
               (float)0
               );
           continue;
        }
        }
        lock.unlock();
    }
}

void OSCthulhuServer::setUserAlive(QString userName) {
    QReadLocker locker(&lock);
    if(users.value(userName)) {
        users.value(userName)->setAliveTimer(0);
    }
}

/*
User* OSCthulhuServer::getUser(QString userName) {
    QReadLocker locker(&lock);
    return users.value(userName);
}
*/

int OSCthulhuServer::getNumUsers() {
    QReadLocker locker(&lock);
    return users.size();
}

int OSCthulhuServer::getPort() {
    return serverPort;
}
QString OSCthulhuServer::getServerPassword() {
    return serverPassword;
}

//===================
//SyncObjects
//===================
bool OSCthulhuServer::addSyncObject(QString name, QString type, QString subtype) {
    QWriteLocker locker(&syncObjectLock);
    if(!this->syncObjects.contains(name)) {
        this->syncObjects.insert(name,new SyncObject(name,type,subtype));
        return true;
    } else {
        return false;
    }
}
void OSCthulhuServer::addSyncArg(QString name, QVariant* argValue) {
    QWriteLocker locker(&syncObjectLock);
    if(this->syncObjects.value(name)) {
        this->syncObjects.value(name)->addArgument(argValue);
    }
}
/*
void OSCthulhuServer::addSyncArg(QString name, QString argName, int argValue) {
    QWriteLocker locker(&syncObjectLock);
    if(this->syncObjects.contains(name)) {
        if(this->syncObjects.value(name)) {
            this->syncObjects.value(name)->addArgument(argName,argValue);
        }
    }
}
void OSCthulhuServer::addSyncArg(QString name, QString argName, float argValue) {
    QWriteLocker locker(&syncObjectLock);
    if(this->syncObjects.contains(name)) {
        if(this->syncObjects.value(name)) {
            this->syncObjects.value(name)->addArgument(argName,argValue);
        }
    }
}
void OSCthulhuServer::addSyncArg(QString name, QString argName, double argValue) {
    QWriteLocker locker(&syncObjectLock);
     if(this->syncObjects.contains(name)) {
        if(this->syncObjects.value(name)) {
            this->syncObjects.value(name)->addArgument(argName,argValue);
        }
    }
}
void OSCthulhuServer::addSyncArg(QString name, QString argName, QString argValue) {
    QWriteLocker locker(&syncObjectLock);
    if(this->syncObjects.contains(name)) {
        if(this->syncObjects.value(name)) {
            this->syncObjects.value(name)->addArgument(argName,argValue);
        }
    }
}
*/
void OSCthulhuServer::setSyncArg(QString name, int argNum, QVariant* argValue,QString inputSync) {
    QWriteLocker locker(&syncObjectLock);
    if(this->syncObjects.contains(name)) {
        if(this->syncObjects.value(name)) {
            this->syncObjects.value(name)->setArgument(argNum,argValue);
            sendSyncArg(name,argNum,inputSync);
        }
    }
}
/*
void OSCthulhuServer::setSyncArg(QString name, QString argName, int argValue) {
    QWriteLocker locker(&syncObjectLock);
    if(this->syncObjects.contains(name)) {
        if(this->syncObjects.value(name)) {
            this->syncObjects.value(name)->setArgument(argName,argValue);
            sendSyncArg(name,argNum);
        }
    }
}
void OSCthulhuServer::setSyncArg(QString name, QString argName, float argValue) {
    QWriteLocker locker(&syncObjectLock);
    if(this->syncObjects.contains(name)) {
        if(this->syncObjects.value(name)) {
            this->syncObjects.value(name)->setArgument(argName,argValue);
            sendSyncArg(name,argName);
        }
    }
}
void OSCthulhuServer::setSyncArg(QString name, QString argName, double argValue) {
    QWriteLocker locker(&syncObjectLock);
    if(this->syncObjects.contains(name)) {
        if(this->syncObjects.value(name)) {
            this->syncObjects.value(name)->setArgument(argName,argValue);
            sendSyncArg(name,argName);
        }
    }
}
void OSCthulhuServer::setSyncArg(QString name, QString argName, QString argValue) {
    QWriteLocker locker(&syncObjectLock);
    if(this->syncObjects.contains(name)) {
        if(this->syncObjects.value(name)) {
            this->syncObjects.value(name)->setArgument(argName,argValue);
            sendSyncArg(name,argName);
        }
    }
}
*/
void OSCthulhuServer::removeSyncObject(QString name) {
    QWriteLocker locker(&syncObjectLock);
    try {
        if(this->syncObjects.contains(name)) {
            if(this->syncObjects.value(name)) {
                delete (syncObjects.value(name));
            }
            this->syncObjects.remove(name);
            sendRemoveSyncObject(name);
        }
    } catch(char *e) {
        qDebug() << "Error trying to remove object: " << e;
    }
}
/*
SyncObject* OSCthulhuServer::getSyncObject(QString name) {
    QReadLocker locker(&syncObjectLock);
    return syncObjects.value(name);
}
*/
/*
QVariant* OSCthulhuServer::getSyncArg(QString name, QString argName) {
    QReadLocker locker(&syncObjectLock);
     return syncObjects.value(name)->getArgument(argName);
}
*/
void OSCthulhuServer::finalizeSyncObject(QString name) {
    QWriteLocker locker(&syncObjectLock);
    if(this->syncObjects.contains(name)) {
        if(this->syncObjects.value(name)) {
            syncObjects.value(name)->finishInitializtion();
        }
    }
    qDebug() << "Server contains " << syncObjects.size() << "SyncObjects.";
    sendSyncObject(name);
}

void OSCthulhuServer::sendRemoveSyncObject(QString name) {
    char buffer[OUTPUT_BUFFER_SIZE];
    osc::OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE );
    p << osc::BeginMessage( "/ServerremoveSyncObject" )
        << qPrintable(name)
        << osc::EndMessage;
    QHashIterator<QString, QPointer<User> > i(users);
    while (i.hasNext()) {
         i.next();
         if(i.value()) {
             if(i.value()->isInitialized()) {
                //i.value()->sendUser(p);
                 this->oscThread->send(i.value().data()->getIp(),p);
            }
         }
    }
}

void OSCthulhuServer::sendSyncObject(QString name) {
    //QReadLocker locker(&syncObjectLock);
    if(syncObjects.value(name)) {
    char buffer[OUTPUT_BUFFER_SIZE];
    osc::OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE );
    p << osc::BeginMessage( "/ServeraddSyncObject" )
        << qPrintable(name)
        << qPrintable(syncObjects.value(name)->getType())
        << qPrintable(syncObjects.value(name)->getSubtype());

    QListIterator<QVariant*> ia(syncObjects.value(name)->getArgs());
    while (ia.hasNext()) {
        QVariant* arg = ia.next();
        if(arg->type() == QVariant::Int) {
            p << arg->toInt();
        } else if(arg->type() == QVariant::Double) {
            p << (float)arg->toDouble();
        } else if(arg->type() == QVariant::String) {
            p << qPrintable(arg->toString());
        } else {
            p << (float)arg->toDouble();
        }
    }
    p << osc::EndMessage;

    QHashIterator<QString, QPointer<User> > i(users);
    while (i.hasNext()) {
         i.next();
         if(i.value()) {
             if(i.value()->isInitialized()) {
                //i.value()->sendUser(p);
                 this->oscThread->send(i.value().data()->getIp(),p);
            }
         }
    }
    }
}

void OSCthulhuServer::sendSyncArg(QString name, int argNum,QString inputSync) {
    //QReadLocker locker(&syncObjectLock);
    if(this->syncObjects.value(name)) {
    char buffer[OUTPUT_BUFFER_SIZE];
    osc::OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE );
    p << osc::BeginMessage( "/ServersetSyncArg" )
        << qPrintable(name)
        << argNum;
    if(syncObjects.value(name)->getArgument(argNum)->type() == QVariant::Int) {
        p << syncObjects.value(name)->getArgument(argNum)->toInt();
    } else if(syncObjects.value(name)->getArgument(argNum)->type() == QVariant::Double) {
        p << (float)syncObjects.value(name)->getArgument(argNum)->toDouble();
    } else if(syncObjects.value(name)->getArgument(argNum)->type( )== QVariant::String) {
        p << qPrintable(syncObjects.value(name)->getArgument(argNum)->toString());
    } else {
        p << (float)(syncObjects.value(name)->getArgument(argNum)->toDouble());
    }
    p << osc::EndMessage;
    QHashIterator<QString, QPointer<User> > i(users);
    while (i.hasNext()) {
         i.next();
         if(i.key().compare(inputSync) != 0) {
            if(i.value()) {
               if(i.value()->isInitialized()) {
                  //i.value()->sendUser(p);
                   this->oscThread->send(i.value().data()->getIp(),p);
              }
           }
        }
    }
    }
}

void OSCthulhuServer::sendStatesync() {
    QReadLocker locker(&lock);
    QReadLocker locker2(&syncObjectLock);

    if(users.size() >0) {
    //osc::OutboundPacketStream p( buffer, SYNC_OUTPUT_BUFFER_SIZE );
        //SEND USERS
        syncPacket.Clear();
        syncPacket << osc::BeginMessage("/currentUsers");
        QHashIterator<QString, QPointer<User> > iu(users);
        while (iu.hasNext()) {
             iu.next();
             syncPacket << qPrintable(iu.value()->getUserName());
        }
        syncPacket << osc::EndMessage;
        sendToUsers(syncPacket);

        syncPacket.Clear();
        if(this->syncObjects.size()) {
        syncPacket << osc::BeginMessage("/ServerSync");
        //SEND SYNCOBJECTS
        QHashIterator<QString, QPointer<SyncObject> > i(syncObjects);
        while (i.hasNext()) {
             i.next();
             if(i.value()) {
                 syncPacket << qPrintable(i.value()->getName())
                 << qPrintable(i.value()->getType())
                 << qPrintable(i.value()->getSubtype());
             QListIterator<QVariant*> ia(i.value()->getArgs());
             while (ia.hasNext()) {
                 QVariant* arg = ia.next();
                 if(arg->type() == QVariant::Int) {
                     syncPacket << arg->toInt();
                 } else if(arg->type() == QVariant::Double) {
                     syncPacket << (float)arg->toDouble();
                 } else if(arg->type() == QVariant::String) {
                     syncPacket << qPrintable(arg->toString());
                 } else {
                     syncPacket << (float)(arg->toDouble());
                 }
             }
             if(i.hasNext()) {
                syncPacket << "0nsync";
             }
         }
        }
        syncPacket<<"0esync";
    syncPacket << osc::EndMessage;
    sendToUsers(syncPacket);
    }
    }
}

// Send to everyone!
void OSCthulhuServer::sendToUsers(osc::OutboundPacketStream p) {
    QHashIterator<QString, QPointer<User> > iu(users);
    while (iu.hasNext()) {
         iu.next();
         if(iu.value()) {
             if(iu.value()->isInitialized()) {
                //iu.value()->sendUser(p);
                 this->oscThread->send(iu.value().data()->getIp(),p);
            }
         }
    }
}

// Send to everyone but the original user.
void OSCthulhuServer::multicastChatToUsers( osc::ReceivedMessage m, QString uN ) {

    char buffer[OUTPUT_BUFFER_SIZE];
    osc::OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE );
    osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
//    /qDebug() << "/multicast from server begin" << endl;
    p << osc::BeginMessage( m.AddressPattern() );

    while( arg != m.ArgumentsEnd() ) {
        //qDebug() << arg->AsString() << endl;
        p << arg->AsString();
        arg++;
    }

    p << osc::EndMessage;
    //qDebug() << "/multicast packet packed on server" << endl;

    QHashIterator<QString, QPointer<User> > iu(users);
    while (iu.hasNext()) {
         iu.next();
         // if initialized AND not the original sender, then send the message
         if(iu.value()) {

            if((iu.value()->getUserName().compare(uN)!=0)) {
               //iu.value()->sendUser(p);
                this->oscThread->send(iu.value().data()->getIp(),p);
           }
        }
    }
}

void OSCthulhuServer::changeUserPiece(QString user, QString piece)
{
    if(users.contains(user)) {
        users.value(user).data()->changePiece(piece);
        char buffer[OUTPUT_BUFFER_SIZE];
        osc::OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE );
        p << osc::BeginMessage( "/changeUserPiece" )
            << qPrintable(user)
            << qPrintable(piece)
            << osc::EndMessage;
        sendToUsers(p);
    }
}

void OSCthulhuServer::cleanup(QString user, QString piece)
{
    QWriteLocker locker(&syncObjectLock);
    QReadLocker locker2(&lock);

    //Log user out of piece
    if(users.contains(user)) {
        users.value(user).data()->changePiece(" ");
        char buffer[OUTPUT_BUFFER_SIZE];
        osc::OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE );
        p << osc::BeginMessage( "/changeUserPiece" )
            << qPrintable(user)
            << qPrintable(" ")
            << osc::EndMessage;
        sendToUsers(p);
    }

    //Check to see if anyone else is logged into the piece
    int pieceCount=0;
    QHashIterator<QString, QPointer<User> > iu(users);
    while (iu.hasNext()) {
         iu.next();
         if(iu.value()) {
             if(iu.value()->isInitialized()) {
                if(iu.value()->getPiece().compare(piece)==0) {
                    pieceCount+=1;
                }
             }
         }
    }

    //If no one else is logged into the piece, remove all objects associated with that piece
    QList<QString> objectDeletionList;
    if(pieceCount==0) {
        QHashIterator<QString, QPointer<SyncObject> > i(syncObjects);
        while (i.hasNext()) {
             i.next();
             if(i.value()) {
                if(i.value()->getType().compare(piece)==0) {
                    objectDeletionList.append(i.value()->getName());
                }
             }
        }
        for(int i=0;i< objectDeletionList.size();i++) {
            qDebug() << "Removing object: " << objectDeletionList.at(i);
            //removeSyncObject(objectDeletionList.at(i));
            try {
                if(this->syncObjects.contains(objectDeletionList.at(i))) {
                    if(this->syncObjects.value(objectDeletionList.at(i))) {
                        delete (syncObjects.value(objectDeletionList.at(i)));
                    }
                    this->syncObjects.remove(objectDeletionList.at(i));
                    sendRemoveSyncObject(objectDeletionList.at(i));
                }
            } catch(char *e) {
                qDebug() << "Error trying to remove object: " << e;
            }
        }
    }
}
